8.5 关键字:static

简介

当我们在C/C++用static修饰变量或函数时,主要有三种用途:

  • 局部静态变量

  • 外部静态变量/函数(废弃,C++标准用匿名命名空间替代)

  • 类内静态数据成员/成员函数(只存在于C++中)

静态变量

1. 静态局部变量

在局部变量前面加上static说明符就构成静态局部变量,例如:

// 声明局部静态变量
static int a;
static int array[5] = {1, 2, 3, 4, 5};
  • 静态局部变量在函数内定义,但不像自动变量那样当函数被调用时就存在,调用结束就消失,静态变量的生存期为整个源程序

  • 静态变量的生存期虽然为整个源程序,但是作用域与自动变量相同,即只能在定义该变量的函数内使用该变量,退出函数后虽然变量还存在,但不能够使用它

  • 对基本类型的静态局部变量如果在声明时未赋初始值,则系统自动赋0值;而对普通局部变量不赋初始值,那么它的值是不确定的

根据静态局部变量的特点,它的生存期为整个源程序,在离开定义它的函数(作用域)但再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量,虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此最好采用局部静态变量。例如:

#include <iostream>

void foo() {
    int j = 0;         // 普通局部变量
    static int k = 0;  // 静态局部变量
    ++j;
    ++k;
    printf("j:%d, k:%d\n", j, k);
}

int main(void)
{
    for (int i = 1; i <= 5; i++) {
        foo();
    }
}

// 输出:
j:1, k:1
j:1, k:2
j:1, k:3
j:1, k:4
j:1, k:5

2. 静态全局变量(C++废弃,用匿名命名空间替代)

Tips:对于全局变量,不管是否被static修饰,它的存储区域都是在静态存储区,生存期为整个源程序。只不过加上static后限制这个全局变量的作用域只能在定义该变量的源文件内。

全局变量(外部变量)的声明之前加上static就构成了静态的全局变量,全局变量本身就是静态存储变量,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同,这两者的区别在于非静态全局变量的作用域是整个源程序。当一个源程序由多个源程序组成时,非静态的全局变量在各个源文件中都是有效的,而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。

这种在文件中进行静态声明的做法是从C语言继承而来的,在C语言中声明为static的全局变量在其所在的文件外不可见。这种做法已经被C++标准取消了,现在的替代做法是使用匿名命名空间。

匿名命名空间:指关键字namespace后紧跟花括号括起来的一系列声明语句,具有如下特点:

  • 在匿名命名空间内定义的变量具有静态生命周期

  • 匿名空间在某个给定的文件内可以不连续,但是不能跨越多个文件

  • 每个文件定义自己的匿名命名空间,不同文件匿名命名空间中定义的名字对应不同实体

  • 如果在一个头文件中定义了匿名命名空间,则该命名空间内定义的名字在每个包含该头文件的文件中对应不同实体

namespace {
    int i;  // 匿名命名空间内定义的变量具有静态生命周期, 作用域仅限于当前文件
}

3. 总结

static这个说明符在不同地方所起的作用域是不同的,比如把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期,把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。

继承与静态成员

1. 基类中的静态成员只有唯一实例

如果一个基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出多少个派生类,对于每个静态成员来说都只存在唯一的实例。

2. 基类中的静态成员的访问控制规则

基类中的静态成员遵循通用的访问控制规则:如果基类中的静态成员是private的,则派生类无权访问它;如果基类中的静态成员是可访问的,则我们既可以通过基类使用它也可以通过派生类使用它。

类模板的static成员

Tips:类似任何其他成员函数,一个static成员函数只有在使用时才会实例化。

与任何其他类一样,类模板可以声明static成员:

template <typename T> class Foo {
 public:
    static size_t count() { return ctr; }
 private:
    static size_t ctr;
};

// 定义并初始化ctr成员
template <typename T>
size_t Foo<T>::ctr = 0;

// 使用static成员
Foo<int> fi;                  // 实例化Foo<int>类和static成员ctr
auto ct = Foo<int>::count();  // 实例化Foo<int>::count
ct = fi.count();              // 使用Foo<>
ct = Foo::count();            // 错误: 没有指定哪个模板实例的count

每个Foo的实例都有一个static成员实例,即对任意给定类型X,都有一个Foo<X>::ctr和一个Foo<X>::count成员,所有Foo<X>类型的对象共享相同的ctr对象和count函数。